package com.wordplat.quickstart.xutils.http.request;

import com.wordplat.quickstart.xutils.cache.DiskCacheEntity;
import com.wordplat.quickstart.xutils.cache.LruDiskCache;
import com.wordplat.quickstart.xutils.common.util.IOUtil;
import com.wordplat.quickstart.xutils.common.util.KeyValue;
import com.wordplat.quickstart.xutils.common.util.LogUtil;
import com.wordplat.quickstart.xutils.common.util.TextUtils;
import com.wordplat.quickstart.xutils.ex.HttpException;
import com.wordplat.quickstart.xutils.http.HttpMethod;
import com.wordplat.quickstart.xutils.http.RequestParams;
import com.wordplat.quickstart.xutils.http.body.ProgressBody;
import com.wordplat.quickstart.xutils.http.body.RequestBody;
import com.wordplat.quickstart.xutils.http.cookie.DbCookieStore;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.net.*;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * Created by wyouflf on 15/7/23.
 * Uri请求发送和数据接收
 *
 * @since 2021-05-09
 */
public class HttpRequest extends UriRequest {
    private static final CookieManager COOKIE_MANAGER =
            new CookieManager(DbCookieStore.INSTANCE, CookiePolicy.ACCEPT_ALL);
    private String cacheKey = null;
    private boolean isLoading = false;
    private InputStream inputStream = null;
    private HttpURLConnection connection = null;
    private int responseCode = 0;

    /**
     * HttpRequest
     *
     * @param params
     * @param loadType
     * @throws Throwable
     */
    public HttpRequest(RequestParams params, Type loadType) throws Throwable {
        super(params, loadType);
    }

    // build query
    @Override
    protected String buildQueryUrl(RequestParams params) throws IOException {
        String uri = params.getUri();
        StringBuilder queryBuilder = new StringBuilder(uri);
        if (!uri.contains("?")) {
            queryBuilder.append("?");
        } else if (!uri.endsWith("?")) {
            queryBuilder.append("&");
        }
        List<KeyValue> queryParams = params.getQueryStringParams();
        if (queryParams != null) {
            for (KeyValue kv : queryParams) {
                String name = kv.key;
                String value = kv.getValueStrOrNull();
                if (!TextUtils.isEmpty(name) && value != null) {
                    queryBuilder.append(URLEncoder.encode(name, params.getCharset()).replaceAll("\\+", "%20"))
                            .append("=")
                            .append(URLEncoder.encode(value, params.getCharset()).replaceAll("\\+", "%20"))
                            .append("&");
                }
            }
        }

        if (queryBuilder.charAt(queryBuilder.length() - 1) == '&') {
            queryBuilder.deleteCharAt(queryBuilder.length() - 1);
        }

        if (queryBuilder.charAt(queryBuilder.length() - 1) == '?') {
            queryBuilder.deleteCharAt(queryBuilder.length() - 1);
        }
        return queryBuilder.toString();
    }

    @Override
    public String getRequestUri() {
        String result = queryUrl;
        if (connection != null) {
            URL url = connection.getURL();
            if (url != null) {
                result = url.toString();
            }
        }
        return result;
    }

    @Override
    public void sendRequest() throws Throwable {
//        isLoading = false;
//        responseCode = 0;
//
//        URL url = new URL(queryUrl);
//
//        Proxy proxy = params.getProxy();
//        if (proxy != null) {
//            connection = (HttpURLConnection) url.openConnection(proxy);
//        } else {
//            connection = (HttpURLConnection) url.openConnection();
//        }
//        connection.setReadTimeout(params.getReadTimeout());
//        connection.setConnectTimeout(params.getConnectTimeout());
//        connection.setInstanceFollowRedirects(params.getRedirectHandler() == null);
//        if (connection instanceof HttpsURLConnection) {
//            SSLSocketFactory sslSocketFactory = params.getSslSocketFactory();
//            if (sslSocketFactory != null) {
//                ((HttpsURLConnection) connection).setSSLSocketFactory(sslSocketFactory);
//            }
//
//            HostnameVerifier hostnameVerifier = params.getHostnameVerifier();
//            if (hostnameVerifier != null) {
//                ((HttpsURLConnection) connection).setHostnameVerifier(hostnameVerifier);
//            }
//        }
//
//        if (params.isUseCookie()) {
//            try {
//                Map<String, List<String>> singleMap =
//                        COOKIE_MANAGER.get(url.toURI(), new HashMap<String, List<String>>(0));
//                List<String> cookies = singleMap.get("Cookie");
//                if (cookies != null) {
//                    connection.setRequestProperty("Cookie", TextUtils.join(";", cookies));
//                }
//            } catch (Throwable ex) {
//                LogUtil.e(ex.getMessage(), ex);
//            }
//        }
//
//        List<RequestParams.Header> headers = params.getHeaders();
//        if (headers != null) {
//            for (RequestParams.Header header : headers) {
//                String name = header.key;
//                String value = header.getValueStrOrNull();
//                if (!TextUtils.isEmpty(name)) {
//                    if (header.setHeader) {
//                        connection.setRequestProperty(name, value);
//                    } else {
//                        connection.addRequestProperty(name, value);
//                    }
//                }
//            }
//        }
//        if (responseParser != null) {
//            responseParser.beforeRequest(this);
//        }
//        if (requestInterceptListener != null) {
//            requestInterceptListener.beforeRequest(this);
//        }
//
//        { // write body
//            HttpMethod method = params.getMethod();
//            try {
//                connection.setRequestMethod(method.toString());
//            } catch (ProtocolException ex) {
//                try { // fix: HttpURLConnection not support PATCH method.
//                    Field methodField = HttpURLConnection.class.getDeclaredField("method");
//                    methodField.setAccessible(true);
//                    methodField.set(connection, method.toString());
//                } catch (Throwable ignored) {
//                    throw ex;
//                }
//            }
//            if (HttpMethod.permitsRequestBody(method)) {
//                RequestBody body = params.getRequestBody();
//                if (body != null) {
//                    if (body instanceof ProgressBody) {
//                        ((ProgressBody) body).setProgressHandler(progressHandler);
//                    }
//                    String contentType = body.getContentType();
//                    if (!TextUtils.isEmpty(contentType)) {
//                        connection.setRequestProperty("Content-Type", contentType);
//                    }
//                    boolean isChunkedMode = false;
//                    long contentLength = body.getContentLength();
//                    if (contentLength < 0) {
//                        connection.setChunkedStreamingMode(256 * 1024);
//                        isChunkedMode = true;
//                    } else {
//                        if (contentLength < Integer.MAX_VALUE) {
//                            connection.setFixedLengthStreamingMode((int) contentLength);
//                        } else {
//                            connection.setChunkedStreamingMode(256 * 1024);
//                            isChunkedMode = true;
//                        }
//                    }
//
//                    if (isChunkedMode) {
//                        connection.setRequestProperty("Transfer-Encoding", "chunked");
//                    } else {
//                        connection.setRequestProperty("Content-Length", String.valueOf(contentLength));
//                    }
//
//                    connection.setDoOutput(true);
//                    body.writeTo(connection.getOutputStream());
//                }
//            }
//        }
//
//        responseCode = connection.getResponseCode();
//        {
//            if (responseParser != null) {
//                responseParser.afterRequest(this);
//            }
//            if (requestInterceptListener != null) {
//                requestInterceptListener.afterRequest(this);
//            }
//        }
//        if (responseCode == 204 || responseCode == 205) { // empty content
//            throw new HttpException(responseCode, this.getResponseMessage());
//        } else if (responseCode >= 300) {
//            HttpException httpException = new HttpException(responseCode, this.getResponseMessage());
//            try {
//                httpException.setResult(IOUtil.readStr(this.getInputStream(), params.getCharset()));
//            } catch (Throwable ex) {
//                LogUtil.w(ex.getMessage(), ex);
//            }
//            LogUtil.e(httpException.toString() + ", url: " + queryUrl);
//            throw httpException;
//        }
//
//        isLoading = true;
    }

    @Override
    public boolean isLoading() {
        return isLoading;
    }

    @Override
    public String getCacheKey() {
        if (cacheKey == null) {
            cacheKey = params.getCacheKey();

            if (TextUtils.isEmpty(cacheKey)) {
                cacheKey = params.toString();
            }
        }
        return cacheKey;
    }

    @Override
    public Object loadResult() throws Throwable {
        isLoading = true;
        return super.loadResult();
    }

    @Override
    public Object loadResultFromCache() throws Throwable {
        isLoading = true;
        DiskCacheEntity cacheEntity = LruDiskCache.getDiskCache(params.getCacheDirName())
                .setMaxSize(params.getCacheSize())
                .get(this.getCacheKey());

        if (cacheEntity != null) {
            if (HttpMethod.permitsCache(params.getMethod())) {
                Date lastModified = cacheEntity.getLastModify();
                if (lastModified.getTime() > 0) {
                    params.setHeader("If-Modified-Since", toGMTString(lastModified));
                }
                String eTag = cacheEntity.getEtag();
                if (!TextUtils.isEmpty(eTag)) {
                    params.setHeader("If-None-Match", eTag);
                }
            }
            return loader.loadFromCache(cacheEntity);
        } else {
            return null;
        }
    }

    @Override
    public void clearCacheHeader() {
        params.setHeader("If-Modified-Since", null);
        params.setHeader("If-None-Match", null);
    }

    @Override
    public InputStream getInputStream() throws IOException {
        if (connection != null && inputStream == null) {
            inputStream = connection.getResponseCode() >= 400
                    ? connection.getErrorStream() : connection.getInputStream();
        }
        return inputStream;
    }

    @Override
    public void close() throws IOException {
        if (inputStream != null) {
            IOUtil.closeQuietly(inputStream);
            inputStream = null;
        }
        if (connection != null) {
            connection.disconnect();
            /**
             * connection = null;
             */
        }
    }

    @Override
    public long getContentLength() {
        long result = -1;
        if (connection != null) {
            try {
                String value = connection.getHeaderField("content-length");
                if (value != null) {
                    result = Long.parseLong(value);
                }
            } catch (Throwable ex) {
                LogUtil.e(ex.getMessage(), ex);
            }
        }
        if (result < 1) {
            try {
                result = this.getInputStream().available();
            } catch (Throwable ignored) {
                String ss = ignored.toString();
            }
        }
        return result;
    }

    @Override
    public int getResponseCode() throws IOException {
        if (connection != null) {
            return responseCode;
        } else {
            if (this.getInputStream() != null) {
                return 200;
            } else {
                return 404;
            }
        }
    }

    @Override
    public String getResponseMessage() throws IOException {
        if (connection != null) {
            return URLDecoder.decode(connection.getResponseMessage(), params.getCharset());
        } else {
            return null;
        }
    }

    @Override
    public long getExpiration() {
        if (connection == null) {
            return -1L;
        }

        long expiration = -1L;

        String cacheControl = connection.getHeaderField("Cache-Control");
        if (!TextUtils.isEmpty(cacheControl)) {
            StringTokenizer tok = new StringTokenizer(cacheControl, ",");
            while (tok.hasMoreTokens()) {
                String token = tok.nextToken().trim().toLowerCase();
                if (token.startsWith("max-age")) {
                    int eqIdx = token.indexOf('=');
                    if (eqIdx > 0) {
                        try {
                            String value = token.substring(eqIdx + 1).trim();
                            long seconds = Long.parseLong(value);
                            if (seconds > 0L) {
                                expiration = System.currentTimeMillis() + seconds * 1000L;
                            }
                        } catch (Throwable ex) {
                            LogUtil.e(ex.getMessage(), ex);
                        }
                    }
                    break;
                }
            }
        }

        // from expires
        if (expiration <= 0L) {
            expiration = connection.getExpiration();
        }

        if (expiration <= 0L && params.getCacheMaxAge() > 0L) {
            expiration = System.currentTimeMillis() + params.getCacheMaxAge();
        }

        if (expiration <= 0L) {
            expiration = Long.MAX_VALUE;
        }
        return expiration;
    }

    @Override
    public long getLastModified() {
        return getHeaderFieldDate("Last-Modified", System.currentTimeMillis());
    }

    @Override
    public String getETag() {
        if (connection == null) {
            return null;
        }
        return connection.getHeaderField("ETag");
    }

    @Override
    public String getResponseHeader(String name) {
        if (connection == null) {
            return null;
        }
        return connection.getHeaderField(name);
    }

    @Override
    public Map<String, List<String>> getResponseHeaders() {
        if (connection == null) {
            return null;
        }
        return connection.getHeaderFields();
    }

    @Override
    public long getHeaderFieldDate(String name, long defaultValue) {
        if (connection == null) {
            return defaultValue;
        }
        return connection.getHeaderFieldDate(name, defaultValue);
    }

    private static String toGMTString(Date date) {
        SimpleDateFormat sdf = new SimpleDateFormat(
                "EEE, dd MMM y HH:mm:ss 'GMT'", Locale.US);
        TimeZone gmtZone = TimeZone.getTimeZone("GMT");
        sdf.setTimeZone(gmtZone);
        return sdf.format(date);
    }
}
